/*
 * University of Nebraska-Lincoln
 * 
 * Course: CSCE896 - AI and Heuristics in Software Engineering
 * Instructor: Dr. Myra Cohen
 * Project: Homework 2 - String Finder using Genetic Algorithm
 * Due: Oct 1, 2012
 * 
 * Author: Thammasak Thianniwet
 * 
 * Description:
 * String Finder is a program to find the optimal (in this case known) solution
 * which is the string "AIandHeuristicSE" using Genetic Algorithm.
 * 
 * Genetic Algorithm Properties
 * - Initial Population = 32 chromosomes.
 * - Select 8 best chromosomes of each generation to be the parents.
 * - Crossover those parents (4 pairs) to generate 4 new children.
 *   Yield 16 chromosomes in every generation.
 * - Mutation Rate = 0.03 by default
 * - Four Fitness Modes [1 NEW]
 * - Three Crossover Modes [2 NEW]
 * - Incorporate Hill-Climb in crossover operation [NEW]
 * - Export population statistic to tab limit file. (for GA Mode only)
 * 
 * USAGE GENETIC: java StringGA <fitnessMode: (1,2,3)> <crossoverMode: (1,2,3)> <hillclimb: (integer)> <mutationRate:0.03 (double)> <varyMu%:0.0 (double)> [statFileName]
 * USAGE RANDOM : java StringGA <fitnessMode: (-1,-2,-3)> [statFileName]
 * (use StringGAverbose for running in verbose mode which will show all details of every GA operations)
 * 
 * fitnessMode:
 *   1 - ASCII Distance
 *  -1 - ASCII Distance [RANDOM]
 *   2 - Binary Decision
 *  -2 - Binary Decision [RANDOM]
 *   3 - Good Alleles Control [NEW]
 *  -3 - Good Alleles Control [RANDOM] [NEW]
 * crossoverMode:
 *   1 – One-point crossover
 *   2 - Two-point crossover between any positions [NEW]
 *   3 - Two-point crossover between the same positions [NEW]
 * hillclimb (int): The number of trying to Crossover for better offspring using Hill-Climb approach (greater than 1 to activate crossover hill-climbing).
 * mutationRate (double): The percentage of genes in population space to be mutated.
 * varyMu% (double): The percentage of increasing mutation rate applied when the plateau was detected. [NEW] 
 * statFileName: a file for writing population statistic.
 * 
 * Language: Java Language
 * File List:
 * 1) StringGA.java
 *    This file contains five classes.
 *  - StringGA is the main program controlling all parameters and overall program.
 *  - GeneticAlgorithm is the major class for GA consisting of all genetic operator.
 *  - Chromosome is the class for storing chromosome and its fitness.
 *  - PopulationStat is the class to collect stat information.
 *  - MyUtil is the class consisting of other utility such as Random().
 * 2) StringGAverbose.java
 *    This is an idential main program, but in verbose mode. It also displays all
 * GA information during the operations. Note that there is only one class in this
 * source file, and can be compiled only after StringGA.java.
 * 
 * @author Thammasak Thianniwet
 * @version StringGA.csce896.FALL2012  2012.10.01
 * 
 */

import java.util.*;
import java.io.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * StringGAverbose is the main program controlling all parameters and overall program.
 * It is differ from the StringGA in case of it shows all details of every GA operations.
 * (verbose = true)
 * 
 * @author Thammasak Thianniwet
 */
public class StringGAverbose {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        int count = 0;
        int countLimit = 1000000;  // Limit number of generations
        int plaALL = 0;

        String paramPrint = new String();
        String usagePrint = new String();
            usagePrint += String.format("USAGE GENETIC: java StringGA <fitnessMode: (1,2,3)> <crossoverMode: (1,2,3)> <hillclimb: (integer)> <mutationRate:0.03 (double)> <varyMu%%:0.0 (double)> [statFileName]\n");
            usagePrint += String.format("USAGE RANDOM : java StringGA <fitnessMode: (-1,-2,-3)> [statFileName]\n");
            usagePrint += String.format("fitnessMode:\n");
            usagePrint += String.format("  1 - ASCII Distance\n");
            usagePrint += String.format(" -1 - ASCII Distance [RANDOM]\n");
            usagePrint += String.format("  2 - Binary Decision\n");
            usagePrint += String.format(" -2 - Binary Decision [RANDOM]\n");
            usagePrint += String.format("  3 - Good Alleles Control [NEW]\n");
            usagePrint += String.format(" -3 - Good Alleles Control [RANDOM] [NEW]\n");
            usagePrint += String.format("crossoverMode:\n");
            usagePrint += String.format("  1 - One-point crossover\n");
            usagePrint += String.format("  2 - Two-point crossover between any positions [NEW]\n");
            usagePrint += String.format("  3 - Two-point crossover between the same positions [NEW]\n");
            usagePrint += String.format("hillclimb (int): The number of trying to Crossover for better offspring using Hill-Climb approach\n");
            usagePrint += String.format("                  ( greater than 1 to activate Hill-Climb mode )\n");
            usagePrint += String.format("mutationRate (double): The percentage of genes in population space to be mutated\n");
            usagePrint += String.format("varyMu%% (double): The percentage of increasing mutation rate applied when the plateau was detected. [NEW] \n");
            usagePrint += String.format("statFileName: A file for writing population statistic\n");
        
        GeneticAlgorithm ga = new GeneticAlgorithm();
        Chromosome c = new Chromosome();
        
        boolean err = false;    // argument error checking. false is no error found.

        /**
         * Read all arguments
         */
        
        /**
         * Collecting parameters
         */
        if(args.length<=0)
            err = true;
        else
            ga.fitnessMode      = Integer.parseInt(args[0]);

        // GA MODE
        if(ga.fitnessMode>0){
            if(args.length>4){
                ga.crossoverMode        = Integer.parseInt(args[1]);
                ga.hillClimbCrossover   = Integer.parseInt(args[2]);
                ga.mutationRate         = Double.parseDouble(args[3]);
                ga.varyMuRate           = Double.parseDouble(args[4]);
                if(args.length>5)
                    ga.statFileName = args[5];
            }
            // If wrong number of arguments was found
            else
                err = true;
        }
        // RANDOM MODE
        else{
            if(args.length==2)
                ga.statFileName = args[1];
            else
                err = true;
            ga.crossoverMode = -1;      // no crossover, randomly pick new nChromosome/2 offsprings
            ga.hillClimbCrossover = 1;  // non hill-climb
            ga.mutationRate = 0.0;      // no mutation
        }
        
        if(err){
            System.out.println(usagePrint);
            System.exit(0);
        }
        
        
        /***************************************
         *  verbose flag
         */
        ga.verbose = true;
        //**************************************
        
        
        /**
         * Print all parameters
         */
        if(ga.fitnessMode<0)         // PURELY RANDOM MODE
            paramPrint += String.format("RANDOM MODE\n");

        paramPrint += String.format("Fitness Mode: ");;
        switch(Math.abs(ga.fitnessMode)){
            case 1: paramPrint += String.format("1 - ASCII Distance\n");
                    break;
            case 2: paramPrint += String.format("2 - Binary Decision\n");
                    break;
            case 3: paramPrint += String.format("3 - Good Alleles Control [NEW]\n");
                    break;
            default: err = true;
        }
        paramPrint += String.format("Crossover Rate: ");
        switch(ga.crossoverMode){
            case -1: paramPrint += String.format(" - RANDOM, without crossover\n");
                    break;
            case 1: paramPrint += String.format("1 - One-point crossover\n");
                    break;
            case 2: paramPrint += String.format("2 - Two-point crossover between any positions [NEW]\n");
                    break;
            case 3: paramPrint += String.format("3 - Two-point crossover between the same positions [NEW]\n");
                    break;
            default: err = true;
        }
        if(ga.hillClimbCrossover>1)
            paramPrint += String.format("Hill-Climb Limit: %d iterations per crossover. [NEW]\n", ga.hillClimbCrossover);
        paramPrint += String.format("Mutation Rate: %f\n", ga.mutationRate);
        if(ga.varyMuRate!=0)
            paramPrint += String.format("  increase %.1f%% mutation rate when plateau was detected. [NEW]\n", ga.varyMuRate*100);
        if(ga.statFileName!=null)
            paramPrint += String.format("Stat File: %s\n",ga.statFileName);
        paramPrint += String.format("\n");

        
        // If anything error, such as wrong parameter was found, then exit.
        if(err){
            System.out.println(usagePrint);
            System.exit(0);
        }else
            System.out.println(paramPrint);
        
        
        /**
         * Write Stat header file
         */
        if(ga.statFileName!=null){
            paramPrint += String.format("%s\t%s\t%s\t%s\t%s\t%s\t%s\n\n", "BEST","WORST","MEAN","SD","PLATEAU","GOOD_ALLE","BAD_ALLE");

            try {
                FileWriter fstream = new FileWriter(ga.statFileName);
//                FileWriter fstream = new FileWriter("/Volumes/IDATA/WORKSPACE/NetBeans/StringGA/"+ga.statFileName);
                BufferedWriter out = new BufferedWriter(fstream);
                out.write(paramPrint);
                out.close();
                fstream.close();
            } catch (IOException ex) {
        //            Logger.getLogger(GeneticAlgorithm.class.getName()).log(Level.SEVERE, null, ex);
                System.out.println("Write error !!!");
                System.exit(0);
            }         
        }
        
        
        
        /*************
         * Generate population until find the optimal solution or exceed iteration limit.
         */
        System.out.println(String.format("%-4s| %-16s | %-23s | %s | %-6s |%s| %s", "GEN","BEST CHROMOSOME","MATCH (#)", "AEHISacdeinrstu", "FITNESS", "DIST", "COMMENT"));
        ga.initPopulation(ga.nChromosome*2); // Doubling chromosomes twice only for the first gen.
        while(true){
            count++;

            c = ga.generate();  // select(); crossover(); mutate();
            plaALL += ga.pStat.plateau;

//            // Print progress
//            if(count%(countLimit/countLimit)==0 || c.getFitness()==0 || count==countLimit || count==1){
//                System.out.println(String.format("%4d| %s | %s | %7d | %.4f| %s",count, c.getChromosome(), ga.visualCorrect(c), ga.pStat.min, (ga.cntPlateau>=5?ga.mutationRate*1.1>1?1:ga.mutationRate*1.1:ga.mutationRate),(ga.cntPlateau>=5?"PLATEAU":"")));
//            }

            // Stopping Criteria
            if(c.getFitness()==0 || count==countLimit){
                if(count==countLimit)
                    System.out.println("Cannot find the solution!");
                else{
                    System.out.println(String.format("The solution was found in %d generations.", count));
                    System.out.println(String.format("Plateau detected: %d (%.1f%%) generations.", plaALL, (double)plaALL/count*100));
                    if(ga.hillClimbCrossover>1)
                        System.out.println("The number of iterations over hill-climb: "+ga.cntHill);
                }
                break;
            }
//            break;
        }
//        ga.printPopulation();
    }
}